﻿using System;
using System.Collections.Generic;
using System.Drawing;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using TgcViewer;
using TgcViewer.Utils.TgcGeometry;
using TgcViewer.Utils.TgcKeyFrameLoader;

namespace AlumnoEjemplos.zanellaSports
{
    public class Vehicle
    {
        //Indice de Aceleracion, tanto para adelante como para atras
        public float indiceAceleracion;
        public float multiVelocidad;
        //Disminucion del torque a medida que aumenta la velocidad de manera unitaria
        public float preJerk;
        //Torque maximoteorico del vehiculo
        public float finalJerk;
        //Indice de Desaceleracion (rozamiento) en x,y. Y la acelaracion gravitatoria (se nesecitaria soporte para Slide contra el piso, hasta entonces 0)
        public Vector3 indiceRozamiento;
        //VectorVelocidad a usar en este ciclo
        public Vector3 vectorVelocidad;
        //Vector de aceleracion actualector de aceleracion actual
        public Vector3 vectorAceleracion;
        public Vector3 velocidadAngular;
        public float cantidadRotacion;
        // Versor que indica la direccion hacia donde mira el vehiculo
        public Vector3 versorCoche;

        public Modelo modelo;
        //Cosntructor
        public Vehicle()
        {
            //Inicializa las variables dinamicas
            versorCoche = new Vector3(0f, 0f, 0f);
            vectorVelocidad = new Vector3(0f, 0f, 0f);
            vectorAceleracion = new Vector3(0f, 0f, 0f);
            velocidadAngular = new Vector3(0f, 0f, 0f);

            //Valores por defecto caracteristicos del vehiculo
            indiceAceleracion = 3f;
            multiVelocidad = 5f;
            indiceRozamiento = new Vector3(0.4f, 0f, 0.1f);
            //El preJerk y el finalJerk simulan el torque del automovil y su accion dependiendo de la velocidad
            //La relacion representa:
            // Velocidad Final Teorica = finalJerk / preJerk (esta no tiene en cuenta la accion del rozamiento, en la practica sera ligeramente menor) 
            // La proporcion aceleracion inicial - acelearcion final se ve afectada por el tamaño de ambas variables. Por ejemplo:
            // Caso 1: preJerk = 0.15 y finalJerk = 240, determinan una velocidad teorica final de 240 kmh
            // Case 2: preJerk = 0.3 y finalJerk = 480, tambien determinan una velocidad final de 240 kmh
            // Sin embargo en el caso 1 la aceleracion inicial por segundo sera aproximadamente la mitad a la del caso 2
            // y al poseer mayor torque en el caso 2, el efecto del rozamiento tmb sera menor 
            // por lo que la velocidad maxima real incrementara (pero nunca pasando el maximo teorico)
            preJerk = 0.2f;
            finalJerk = 30f;

            //Creo el modelo que utilizara este vehiculo
            modelo = new Modelo();
        }

        /// <summary>
        /// Metodo el cual recibe un input (tanto del usuario como del modulo de AI)
        /// y crea un vector aceleracion, modificando el vector velocidad para ver reflejado el mismo
        /// </summary>
        public void update(Vector2 input, float elapsedTime)
        {
            //Instancia de la gui para obetener el tiempo de frame
            //float elapsedTime = GuiController.Instance.ElapsedTime;

            float moduloVectorVelocidad = General.moduloVector2(new Vector2(vectorVelocidad.X, vectorVelocidad.Z));
            float acelerar = input.X, girar = input.Y;

            //Establesco la accion del Jerk
            float jerk = (finalJerk - moduloVectorVelocidad * preJerk);

            //Establesco la aceleracion, teniendo en cuenta su variacion (tiron o jerk)
            //La direccion del vector aceleracion es la misma del coche
            if (acelerar != 0)
            {
                vectorAceleracion.X = FastMath.Sin(modelo.rotation().Y) * jerk;
                vectorAceleracion.Z = FastMath.Cos(modelo.rotation().Y) * jerk;
                //if (acelerar == -1)
                //    vectorAceleracion = -vectorAceleracion;
                vectorAceleracion = vectorAceleracion * acelerar;
            }
            else
            {
                vectorAceleracion.Z = 0;
                vectorAceleracion.X = 0;
            }

            //Aplico la aceleracion al vector velocidad
            vectorVelocidad += (indiceAceleracion * vectorAceleracion * elapsedTime);

            //Calculo el vector rozamiento (no se tiene en cuanta ningun rozamiento en el eje y)
            //El vector rozamiento estara formado por dos componentes, uno perpendicular al vehiculo y otro paralelo mucho menor.
            Vector3 vectorRozamientoCompParalelo = new Vector3();
            Vector3 vectorRozamientoCompPerpendicular = new Vector3();

            //Determino la direccion de moviemiento con un versor
            Vector3 versorVelocidad = new Vector3(vectorVelocidad.X / moduloVectorVelocidad, 0, vectorVelocidad.Z / moduloVectorVelocidad);

            //Determino la direccion hacia donde mira el coche con un versor
            versorCoche = new Vector3(FastMath.Sin(modelo.rotation().Y), 0, FastMath.Cos(modelo.rotation().Y));

            //Calculo el angulo entre los vectores velocidad y sentido hacia al que mira el auto. Se aprovecha la regla de la mano derecha en el producto
            //vectorial para saber la orientacion del rozamiento
            float anguloEntreVelocidadYCoche = General.anguloEntreVersores(versorVelocidad, versorCoche);

            if (anguloEntreVelocidadYCoche > 0.01f && moduloVectorVelocidad > 0.01f)
                if ((Vector3.Cross(versorCoche, versorVelocidad)).Y > 0)
                    vectorRozamientoCompPerpendicular = new Vector3(-versorCoche.Z, 0, versorCoche.X) * 45;
                else
                    vectorRozamientoCompPerpendicular = new Vector3(-versorCoche.Z, 0, versorCoche.X) * -45;

            //Calculo el sentido de la componente paralela basando en si el angulo entre los versores en mayor que pi/2, tonces el sentido del mismo sera contrario al del vehiculo
            if (anguloEntreVelocidadYCoche > 1.57)
                vectorRozamientoCompParalelo = versorCoche * (5);
            else
                vectorRozamientoCompParalelo = versorCoche * (-5);

            //Aplico el vector rozamiento (si el auto esta en movimiento)
            if (moduloVectorVelocidad > 0.1f)
                vectorVelocidad += (vectorRozamientoCompParalelo + vectorRozamientoCompPerpendicular) * elapsedTime;

            //Actualizo la variable que almacena el modulo del vector velocidad
            moduloVectorVelocidad = General.moduloVector2(new Vector2(vectorVelocidad.X, vectorVelocidad.Z));

            //Muevo el modelo segun la velocidad por el tiempo transcurrido
            modelo.move(vectorVelocidad * elapsedTime * multiVelocidad);

            //Esta comprobación es porque la imprecisión del float impide que la velocidad se haga cero.
            if (moduloVectorVelocidad < 0.15f)
            {
                vectorVelocidad.X = 0;
                vectorVelocidad.Y = 0;
                vectorVelocidad.Z = 0;
            }

            //Actualizo la variable que almacena el modulo del vector velocidad
            moduloVectorVelocidad = (int)General.moduloVector2(new Vector2(vectorVelocidad.X, vectorVelocidad.Z));

            //Giro el modelo y el vector aceleracion, si hay input de giro
            cantidadRotacion = moduloVectorVelocidad;
            //Aprobecho el signo del flag para cambiar el sentido de rotacion
            cantidadRotacion = cantidadRotacion * girar * 0.7f;
            if (cantidadRotacion != 0)
            {
                modelo.rotateY(Geometry.DegreeToRadian(cantidadRotacion) * elapsedTime);
                //Al girar las ruedas un porcentaje del vector velocidad se redirecciona, el resto se pierde contra el rozamiento
                vectorVelocidad = General.RotarVectorY(vectorVelocidad, Geometry.DegreeToRadian(cantidadRotacion * (1 - moduloVectorVelocidad / 200)) * elapsedTime);
                if (cantidadRotacion < 0)
                    modelo.turnLeft();
                else
                    modelo.turnRight();
            }
            else
                modelo.run();
        }

        /// <summary>
        /// Metodo el cual se encarga de realizar el render del vehiculo
        /// </summary>
        public void render(float elapsedTime)
        {
            modelo.render(elapsedTime);
        }

        public Vector3 Position()
        {
            return modelo.Position();
        }

        public void dispose()
        {
            modelo.dispose();
        }
    }

}